﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IndentityConfigUtility.cs" company="DST Nexdox">
//   Copyright (c) DST Nexdox. All rights reserved.
// </copyright>
// <summary>
//   Defines the IndentityConfigUtility type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace IdentityWrapper.Identity
{
  using System;
  using System.Collections.Generic;
  using System.Configuration;
  using System.Data.Entity;
  using System.Linq;
  using System.Text;
  using Entities;
  using Exceptions;
  using Microsoft.AspNet.Identity;
  using Microsoft.AspNet.Identity.EntityFramework;
  using UnityRepository.Contexts;

  // TODO: Some or all of this logic should go in UnityRepository.Migrations.Configuration.Seed
  public static class IndentityConfigUtility
  {
    private const String RootAdminRole = "dstadmin";
    private const String AdminRole = "Admin";
    private const String ReadOnlyRole = "Read Only";
    private const String GovernorRole = "Governor";

    private static readonly UnityDbContext Context = new UnityDbContext(ConfigurationManager.ConnectionStrings["Unity"].ConnectionString);

    public static void Initialize()
    {
      CreateDefaultRolesIfMissing();

      CreateAdminUserIfMissing();

      AssignTestUserToReadOnlyRoleIfApplicable();
    }

    private static void CreateDefaultRolesIfMissing()
    {
      String[] roles = { RootAdminRole, AdminRole, ReadOnlyRole, GovernorRole };

      foreach (String role in roles)
      {
        CreateRoleIfDoesNotExist(role);
      }
    }

    private static void CreateAdminUserIfMissing()
    {
      const String UserName = "dstadmin";
      const String Password = "Password123";

      var newAdmin = new ApplicationUser(UserName)
                       {
                         PasswordHash = Password,
                         LastLoginDate = DateTime.Now,
                         LastPasswordChangedDate = DateTime.Now
                       };

      ApplicationUser user = CreateIfUserDoesNotExist(newAdmin, Password, RootAdminRole);

      AddOrUpdateDomicileIdsForAdmin(user);
      AddOrUpdateManCoIdsForAdmin(user);
    }

    private static void AssignTestUserToReadOnlyRoleIfApplicable()
    {
      /*
       * This in particular should have gone in the Seed method (see class Comment), however that
       * was not easy as no Role Entity exists, and using other projects create circular references
       */

      const String UserName = "Test";

      var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(Context));

      ApplicationUser testUser = userManager.FindByName(UserName);

      if (testUser != null)
      {
        AddToRoleIfUnassigned(testUser.Id, ReadOnlyRole);
      }
    }

    private static ApplicationUser CreateIfUserDoesNotExist(ApplicationUser newUser, String password, String role)
    {
        var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(Context));

        var userResult = new IdentityResult();

        ApplicationUser user = userManager.FindByName(newUser.UserName);

        if (user == null)
        {
            userResult = userManager.Create(newUser, password);
        }

        if (userResult.Succeeded)
        {
            AddToRole(newUser.Id, role);
            user = newUser;
        }

        return user;
    }

    private static void AddToRoleIfUnassigned(String userId, String roleName)
    {
      CreateRoleIfDoesNotExist(roleName);

      var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(Context));
      if (!userManager.IsInRole(userId, roleName))
      {
        userManager.AddToRole(userId, roleName);
      }
    }

    private static void AddToRole(String id, String role)
    {
      CreateRoleIfDoesNotExist(role);

      var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(Context));

      userManager.AddToRole(id, role);
    }

    private static void CreateRoleIfDoesNotExist(String roleName)
    {
      var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(Context));

      if (!roleManager.RoleExists(roleName))
      {
        IdentityResult result = roleManager.Create(new IdentityRole(roleName));

        if (result.Succeeded)
        {
          return;
        }

        var errorMessage = new StringBuilder();
        errorMessage.AppendFormat("Failed to create role '{0}'", roleName);
        foreach (String underlyingError in result.Errors)
        {
          errorMessage.AppendFormat("{0}{1}", Environment.NewLine, underlyingError);
        }

        throw new UnityException(errorMessage.ToString());
      }
    }

    private static List<Int32> GetDomicileIds()
    {
       return Context.Domicile.ToList().Select(x => x.Id).ToList();
    }

    private static List<Int32> GetManCoIds()
    {
        return Context.Mancos.ToList().Select(x => x.Id).ToList();
    }

    private static void AddOrUpdateManCoIdsForAdmin(ApplicationUser user)
    {
      List<Int32> listManCoIds = GetManCoIds();

      List<Int32> listUserManCoIds = user.ManCos.Select(x => x.ManCoId).ToList();

      if (!listManCoIds.OrderBy(i => i).SequenceEqual(listUserManCoIds.OrderBy(i => i)))
      {
        foreach (Int32 manCoId in listManCoIds)
          {
             if (!listUserManCoIds.Contains(manCoId))
             {
                 user.ManCos.Add(new ApplicationUserManCo { UserId = user.Id, ManCoId = manCoId });
             }
          } 
      }

      Context.Entry(user).State = EntityState.Modified;
      Context.SaveChanges();
    }

    private static void AddOrUpdateDomicileIdsForAdmin(ApplicationUser user)
    {
      List<Int32> listDomicileIds = GetDomicileIds();

      List<Int32> listUserDomicileIds = user.Domiciles.Select(x => x.DomicileId).ToList();

      if (!listDomicileIds.OrderBy(i => i).SequenceEqual(listUserDomicileIds.OrderBy(i => i)))
      {
          foreach (Int32 domicileId in listDomicileIds)
          {
             if (!listUserDomicileIds.Contains(domicileId))
             {
                 user.Domiciles.Add(new ApplicationUserDomicile { UserId = user.Id, DomicileId = domicileId });
             }
          }
      }

      Context.Entry(user).State = EntityState.Modified;
      Context.SaveChanges();
    }
  }
}
